{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d1822a7d-5698-4f4d-92ec-7f712fac9d49",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "import torch\n",
    "import pandas as pd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "6831f6f9-c45e-4e48-82e9-e8f9b02d207f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "device(type='cuda')"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 检查是否有可用的 GPU\n",
    "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "device"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c80ff877-7d82-463d-b0f5-96a346ef5363",
   "metadata": {},
   "source": [
    "## 构造数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1816afb0-2565-4fbd-917d-54c4b391fc9e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据数量333248\n",
      "                                                  content  stars  label\n",
      "67672   杰森斯坦森的片儿看起来都比较过瘾，剧情其次哈哈！！剪辑比较失败好多交代不明白或者草草略过，但...      4      1\n",
      "14223                                       小时候看的~当时惊为天人~      4      1\n",
      "209125  谈及尺八与长城间相似的温柔和孤独，小凑昭尚长城之上于已故友人间的共鸣，没有苦涩，更像宽慰，哀...      5      1\n",
      "63065                                           火山爆发，极限逃生      4      1\n",
      "140220  3D cinema class screening. 2D转3D好像没有什么必要，不过结尾的...      4      1\n"
     ]
    }
   ],
   "source": [
    "# 读取数据集\n",
    "data_path = '../datasets/chinese_movie_reviews/chinese_movie_reviews_datasets.jsonl'\n",
    "df = pd.read_json(data_path, orient='records', lines=True)\n",
    "\n",
    "# star > 3标记为 1，star < 3 标记为0。去除star==3的数据，去除star避免中性评论干扰模型\n",
    "df['label'] = df['stars'].apply(lambda x: 1 if x > 3 else (0 if x < 3 else None))\n",
    "df = df.dropna(subset=['label'])\n",
    "df['label'] = df['label'].astype(int)  # 转换为整数类型\n",
    "\n",
    "print(f'数据数量{len(df)}')\n",
    "print(df.sample(5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "6da13e00-6a47-453a-8faf-fd551b5c8ac4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "最大文本长度：1953\n",
      "平均文本长度：62.20\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>content</th>\n",
       "      <th>stars</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>175591</th>\n",
       "      <td>这套光盘至今还在~</td>\n",
       "      <td>5</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>347259</th>\n",
       "      <td>看到最后的时候笑了。就这？非常嫌疑犯？</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>246612</th>\n",
       "      <td>冲着男主的那张帅脸亲上去！</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>111445</th>\n",
       "      <td>非常好看的一部电影，很有武侠味道！！！真的怀念以前的古装剧</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>65906</th>\n",
       "      <td>Sofía Vergara~~~</td>\n",
       "      <td>4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                              content  stars  label\n",
       "175591                      这套光盘至今还在~      5      1\n",
       "347259            看到最后的时候笑了。就这？非常嫌疑犯？      2      0\n",
       "246612                  冲着男主的那张帅脸亲上去！      4      1\n",
       "111445  非常好看的一部电影，很有武侠味道！！！真的怀念以前的古装剧      4      1\n",
       "65906                Sofía Vergara~~~      4      1"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 计算每条文本的长度\n",
    "sentence_lengths = df[\"content\"].apply(lambda x: len(x))  # 计算每条文本的词数（已经分词）\n",
    "# 计算最大长度和平均长度\n",
    "max_length = sentence_lengths.max()\n",
    "avg_length = sentence_lengths.mean()\n",
    "print(f\"最大文本长度：{max_length}\")\n",
    "print(f\"平均文本长度：{avg_length:.2f}\")\n",
    "\n",
    "df.sample(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "8e7597b4-8d89-4f34-9a74-fc4c6576b903",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "训练集大小: 299923\n",
      "测试集大小: 33325\n"
     ]
    }
   ],
   "source": [
    "# 划分训练集和测试集 (这里使用90/10划分)\n",
    "train_df, test_df = train_test_split(df, test_size=0.1, stratify=df['label'])\n",
    "\n",
    "print(f\"训练集大小: {len(train_df)}\")\n",
    "print(f\"测试集大小: {len(test_df)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "d798fe2c-4a7b-40c6-853e-659d2fcc8106",
   "metadata": {},
   "outputs": [],
   "source": [
    "from transformers import BertTokenizer, BertForSequenceClassification, AdamW, get_linear_schedule_with_warmup\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "import os\n",
    "\n",
    "# 定义分词器路径 这里使用 Bert 的分词器\n",
    "# 需提前下载好分词器文件\n",
    "tokenizer_path = '../models/3_Transformer_Sentiment_Classification/bert-base-chinese'\n",
    "tokenizer = BertTokenizer.from_pretrained(tokenizer_path)\n",
    "    \n",
    "max_len = 128\n",
    "\n",
    "# 创建自定义Dataset\n",
    "class MovieReviewDataset(Dataset):\n",
    "    def __init__(self, reviews, labels, tokenizer, max_len):\n",
    "        self.reviews = reviews\n",
    "        self.labels = labels\n",
    "        self.tokenizer = tokenizer\n",
    "        self.max_len = max_len\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.reviews)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        review = str(self.reviews[idx])\n",
    "        label = self.labels[idx]\n",
    "\n",
    "        encoding = self.tokenizer.encode_plus(\n",
    "            review,\n",
    "            add_special_tokens=True, # 添加 [CLS]（用于分类）和 [SEP]（分隔符）\n",
    "            max_length=self.max_len,\n",
    "            return_token_type_ids=False,\n",
    "            padding='max_length', # 短序列填充到 max_len\n",
    "            truncation=True,      # 长序列截断到 max_len\n",
    "            return_attention_mask=True,\n",
    "            return_tensors='pt',  # 返回PyTorch张量\n",
    "        )\n",
    "\n",
    "        return {\n",
    "            # 'review_text': review,\n",
    "            'input_ids': encoding['input_ids'].flatten(),\n",
    "            'attention_mask': encoding['attention_mask'].flatten(),\n",
    "            'labels': torch.tensor(label, dtype=torch.long)\n",
    "        }\n",
    "\n",
    "train_dataset = MovieReviewDataset(train_df.content.to_numpy(), train_df.label.to_numpy(), tokenizer, max_len)\n",
    "test_dataset = MovieReviewDataset(test_df.content.to_numpy(), test_df.label.to_numpy(), tokenizer, max_len)\n",
    "\n",
    "train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\n",
    "test_loader = DataLoader(test_dataset, batch_size=32)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0d58254-08f2-43fc-a87e-23b4943cfafb",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8559dadf-c087-4650-a68c-cee6efcfeef5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "import math\n",
    "\n",
    "class TransformerClassifier(nn.Module):\n",
    "    def __init__(self, vocab_size, embed_dim, num_heads, num_encoder_layers, ff_dim, num_classes,\n",
    "                     max_len=512, dropout_rate=0.1):\n",
    "        \"\"\"\n",
    "        初始化 Transformer 分类器\n",
    "        \n",
    "        Args:\n",
    "            vocab_size (int): 词汇表大小（tokenizer.vocab_size）。\n",
    "            embed_dim (int): 词嵌入和 Transformer 的维度（d_model）。\n",
    "            num_heads (int): 多头注意力机制的头数，必须能整除 embed_dim。\n",
    "            num_encoder_layers (int): Transformer Encoder 的层数。\n",
    "            ff_dim (int): 前馈网络中间层的维度（通常为 embed_dim 的 2-4 倍）。\n",
    "            num_classes (int): 分类任务的类别数（2 表示正/负）。\n",
    "            max_len (int): 最大序列长度，用于位置嵌入。\n",
    "            dropout_rate (float): Dropout 比率，用于正则化。\n",
    "        \"\"\"\n",
    "        super().__init__()\n",
    "        self.embed_dim = embed_dim\n",
    "        # 词嵌入层，将 token ID 映射到 embed_dim 维向量\n",
    "        self.token_embedding = nn.Embedding(vocab_size, embed_dim)\n",
    "        # 可学习的位置嵌入，为每个位置生成 embed_dim 维向量\n",
    "        self.positional_embedding = nn.Embedding(max_len, embed_dim)\n",
    "\n",
    "        # 定义单个 Transformer Encoder 层\n",
    "        encoder_layer = nn.TransformerEncoderLayer(\n",
    "            d_model=embed_dim,              # 模型维度\n",
    "            nhead=num_heads,                # 注意力头数\n",
    "            dim_feedforward=ff_dim,         # 前馈网络中间层维度\n",
    "            dropout=dropout_rate,            # Dropout 比率\n",
    "            batch_first=True,               # 输入/输出形状为 (batch, seq, feature)，适配常见数据格式\n",
    "            activation='gelu'               # 使用 GELU 激活函数，相比 ReLU 更平滑，有助于梯度流动\n",
    "        )\n",
    "\n",
    "        # 堆叠多个 Transformer Encoder 层\n",
    "        self.transformer_encoder = nn.TransformerEncoder(\n",
    "            encoder_layer,\n",
    "            num_layers=num_encoder_layers,\n",
    "            norm=nn.LayerNorm(embed_dim)    # 显式添加 LayerNorm，规范化输出\n",
    "        )\n",
    "\n",
    "        self.dropout = nn.Dropout(dropout_rate)\n",
    "        \n",
    "        # 分类头：将 [CLS] token 的输出（embed_dim 维）映射到 num_classes 维\n",
    "        self.fc_out = nn.Linear(embed_dim, num_classes)\n",
    "\n",
    "        self.max_len = max_len # 存储最大序列长度，供位置编码使用\n",
    "\n",
    "        self._init_weights()\n",
    "\n",
    "    def _init_weights(self):\n",
    "        \"\"\"\n",
    "        初始化模型权重，使用 Xavier Uniform 初始化，适合 Transformer 模型。\n",
    "        避免初始权重过大或过小，加速收敛。\n",
    "        \"\"\"\n",
    "        for p in self.parameters():\n",
    "            if p.dim() > 1:  # 仅对二维以上参数（如线性层、嵌入层）应用\n",
    "                nn.init.xavier_uniform_(p)\n",
    "            # 对嵌入层可额外应用正态初始化\n",
    "            elif p.dim() == 2 and 'embedding' in p.name:\n",
    "                nn.init.normal_(p, mean=0.0, std=0.02)\n",
    "\n",
    "    def forward(self, input_ids, attention_mask):\n",
    "        \"\"\"\n",
    "        前向传播，处理输入序列并输出分类 logits。\n",
    "\n",
    "        Args:\n",
    "            input_ids (torch.Tensor): 形状 (batch_size, seq_len)，词的 ID。\n",
    "            attention_mask (torch.Tensor): 形状 (batch_size, seq_len)，1 表示有效 token，0 表示 padding。\n",
    "\n",
    "        Returns:\n",
    "            torch.Tensor: 形状 (batch_size, num_classes)，分类 logits。\n",
    "        \"\"\"\n",
    "        seq_len = input_ids.size(1)  # 获取序列长度\n",
    "\n",
    "        # 1. 词嵌入\n",
    "        token_embeds = self.token_embedding(input_ids)  # (batch_size, seq_len, embed_dim)\n",
    "        token_embeds = token_embeds * math.sqrt(self.embed_dim) # 缩放嵌入，稳定训练\n",
    "\n",
    "        # 2. 位置编码\n",
    "        # 生成位置索引：(batch_size, seq_len)，每个样本重复 0 到 seq_len-1\n",
    "        positions = torch.arange(0, seq_len, device=input_ids.device).unsqueeze(0).repeat(input_ids.size(0), 1)\n",
    "        position_embeds = self.positional_embedding(positions)  # (batch_size, seq_len, embed_dim)\n",
    "        \n",
    "        # 词嵌入与位置嵌入相加\n",
    "        x = token_embeds + position_embeds\n",
    "        x = self.dropout(x)  # 在嵌入后应用 Dropout，增强鲁棒性\n",
    "\n",
    "        # Transformer Encoder需要 src_key_padding_mask\n",
    "        # attention_mask: 1是token, 0是padding.\n",
    "        # src_key_padding_mask: True表示该位置是padding, 需要被mask掉.\n",
    "        src_key_padding_mask = (attention_mask == 0)  # (batch_size, seq_len)\n",
    "\n",
    "        # 3. Transformer Encoder\n",
    "        # 输入形状: (batch_size, seq_len, embed_dim)\n",
    "        encoder_output = self.transformer_encoder(x, src_key_padding_mask=src_key_padding_mask)\n",
    "        # encoder_output shape: (batch_size, seq_len, embed_dim)\n",
    "\n",
    "        # 4. 分类\n",
    "        # 通常使用第一个token ([CLS] token)的输出来进行分类\n",
    "        cls_output = encoder_output[:, 0, :]  # (batch_size, embed_dim)\n",
    "        # 或者，可以对所有token的输出进行平均池化或最大池化\n",
    "        # cls_output = encoder_output.mean(dim=1) # 平均池化\n",
    "\n",
    "        cls_output = self.dropout(cls_output)\n",
    "        logits = self.fc_out(cls_output)  # (batch_size, num_classes)\n",
    "\n",
    "        return logits\n",
    "\n",
    "# 定义模型超参数\n",
    "VOCAB_SIZE = tokenizer.vocab_size  # 从之前加载的 BERT 分词器获取\n",
    "EMBED_DIM = 256                   # 嵌入维度，较小以减少计算量（BERT 常用 768）\n",
    "NUM_HEADS = 8                     # 多头注意力头数，需满足 embed_dim % num_heads == 0\n",
    "NUM_ENCODER_LAYERS = 4            # Encoder 层数，平衡性能与计算成本\n",
    "FF_DIM = 512                      # 前馈网络中间层维度，通常为 embed_dim 的 2-4 倍\n",
    "NUM_CLASSES = 2                   # 分类任务的类别数（正/负情感）\n",
    "DROPOUT_RATE = 0.1                # Dropout 比率，防止过拟合\n",
    "\n",
    "\n",
    "model = TransformerClassifier(\n",
    "    vocab_size=VOCAB_SIZE,\n",
    "    embed_dim=EMBED_DIM,\n",
    "    num_heads=NUM_HEADS,\n",
    "    num_encoder_layers=NUM_ENCODER_LAYERS,\n",
    "    ff_dim=FF_DIM,\n",
    "    num_classes=NUM_CLASSES,\n",
    "    max_len=max_len, # 从之前的配置中获取\n",
    "    dropout_rate=DROPOUT_RATE\n",
    ")\n",
    "model = model.to(device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "e85517e3-dc17-4ada-9ccd-1a317b17c6a7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TransformerClassifier(\n",
      "  (token_embedding): Embedding(21128, 256)\n",
      "  (positional_embedding): Embedding(128, 256)\n",
      "  (transformer_encoder): TransformerEncoder(\n",
      "    (layers): ModuleList(\n",
      "      (0-3): 4 x TransformerEncoderLayer(\n",
      "        (self_attn): MultiheadAttention(\n",
      "          (out_proj): NonDynamicallyQuantizableLinear(in_features=256, out_features=256, bias=True)\n",
      "        )\n",
      "        (linear1): Linear(in_features=256, out_features=512, bias=True)\n",
      "        (dropout): Dropout(p=0.1, inplace=False)\n",
      "        (linear2): Linear(in_features=512, out_features=256, bias=True)\n",
      "        (norm1): LayerNorm((256,), eps=1e-05, elementwise_affine=True)\n",
      "        (norm2): LayerNorm((256,), eps=1e-05, elementwise_affine=True)\n",
      "        (dropout1): Dropout(p=0.1, inplace=False)\n",
      "        (dropout2): Dropout(p=0.1, inplace=False)\n",
      "      )\n",
      "    )\n",
      "    (norm): LayerNorm((256,), eps=1e-05, elementwise_affine=True)\n",
      "  )\n",
      "  (dropout): Dropout(p=0.1, inplace=False)\n",
      "  (fc_out): Linear(in_features=256, out_features=2, bias=True)\n",
      ")\n",
      "模型总参数数量: 7,550,978\n",
      "模型可训练参数数量: 7,550,978\n"
     ]
    }
   ],
   "source": [
    "# 查看模型结构\n",
    "# 打印模型参数总数和可训练参数总数\n",
    "def count_parameters(model):\n",
    "    total_params = sum(p.numel() for p in model.parameters())  # 所有参数数量\n",
    "    trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)  # 需要训练的参数数量\n",
    "    print(f\"模型总参数数量: {total_params:,}\")\n",
    "    print(f\"模型可训练参数数量: {trainable_params:,}\")\n",
    "\n",
    "print(model)\n",
    "count_parameters(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66a0e293-831e-4ace-8ab3-981be5be48f8",
   "metadata": {},
   "source": [
    "## 模型训练与评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "579f3cd2-4a94-4a86-9bb9-5046855e0018",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "D:\\develop\\anaconda\\envs\\pytorch\\lib\\site-packages\\transformers\\optimization.py:591: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "EPOCHS = 5\n",
    "LEARNING_RATE = 2e-5\n",
    "\n",
    "# AdamW 是 Adam 的变种，增加了权重衰减（weight decay）\n",
    "optimizer = AdamW(\n",
    "    model.parameters(),\n",
    "    lr=LEARNING_RATE,\n",
    "    eps=1e-8,                  # 防止除零的微小值，1e-8 是 Adam 的默认值\n",
    "    weight_decay=0.01          # 添加权重衰减（L2 正则化），防止过拟合，0.01 是常见值\n",
    ")\n",
    "\n",
    "# 计算总训练步数\n",
    "total_steps = len(train_loader) * EPOCHS\n",
    "\n",
    "# 初始化学习率调度器\n",
    "# get_linear_schedule_with_warmup 是一种常见的调度策略：\n",
    "# 1. 在 warmup 阶段，学习率从 0 线性增加到初始学习率\n",
    "# 2. 之后，学习率线性衰减到 0\n",
    "scheduler = get_linear_schedule_with_warmup(\n",
    "    optimizer,                          # 绑定的优化器\n",
    "    num_warmup_steps=int(total_steps * 0.1),  # 预热步数，10% 的总步数，逐步增加学习率以稳定训练\n",
    "    num_training_steps=total_steps      # 总训练步数，用于线性衰减\n",
    ")\n",
    "loss_fn = nn.CrossEntropyLoss().to(device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "fdbfde3d-bcc5-4bbc-b954-d3e576d1cb90",
   "metadata": {},
   "outputs": [],
   "source": [
    "from tqdm import tqdm\n",
    "\n",
    "# 训练函数\n",
    "def train(dataloader, model, loss_fn, optimizer):\n",
    "    model.train()  # 设置模型为训练模式\n",
    "    \n",
    "    running_loss = 0.0\n",
    "    correct = 0\n",
    "    total = 0\n",
    "\n",
    "    # 使用 tqdm 包裹数据加载器，显示进度条\n",
    "    progress_bar = tqdm(dataloader, desc=\"Training\", leave=False)\n",
    "    for item in progress_bar:\n",
    "        # 将数据移动到设备\n",
    "        input_ids = item[\"input_ids\"].to(device)\n",
    "        attention_mask = item[\"attention_mask\"].to(device)\n",
    "        labels = item[\"labels\"].to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        outputs = model(input_ids=input_ids, attention_mask=attention_mask)\n",
    "        loss = loss_fn(outputs, labels)\n",
    "\n",
    "        # 反向传播和优化\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n",
    "        optimizer.step()  # 更新模型参数\n",
    "        scheduler.step()\n",
    "\n",
    "        # 统计指标\n",
    "        running_loss += loss.item()\n",
    "        _, predicted = torch.max(outputs, 1)\n",
    "        total += labels.size(0)\n",
    "        correct += (predicted == labels).sum().item()\n",
    "\n",
    "        # 更新进度条描述\n",
    "        progress_bar.set_postfix(lr=optimizer.param_groups[0]['lr'], loss=loss.item())\n",
    "        \n",
    "    scheduler.step()  # 更新学习率       \n",
    "\n",
    "    accuracy = 100 * correct / total\n",
    "    avg_loss = running_loss / len(dataloader)\n",
    "    return avg_loss, accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "36267fd1-6b5e-4a5e-b9a7-e97652a9948c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 测试函数\n",
    "def evaluate(dataloader, model, loss_fn):\n",
    "    model.eval()  # 设置模型为评估模式\n",
    "    running_loss = 0.0\n",
    "    correct = 0\n",
    "    total = 0\n",
    "\n",
    "    with torch.no_grad():  # 关闭梯度计算\n",
    "        progress_bar = tqdm(dataloader, desc=\"Evaluating\", leave=False)\n",
    "        for item in progress_bar:\n",
    "            # 将数据移动到设备\n",
    "            input_ids = item[\"input_ids\"].to(device)\n",
    "            attention_mask = item[\"attention_mask\"].to(device)\n",
    "            labels = item[\"labels\"].to(device)\n",
    "    \n",
    "            # 前向传播\n",
    "            outputs = model(input_ids=input_ids, attention_mask=attention_mask)\n",
    "            loss = loss_fn(outputs, labels)\n",
    "\n",
    "            # 统计指标\n",
    "            running_loss += loss.item()\n",
    "            _, predicted = torch.max(outputs, 1)\n",
    "            total += labels.size(0)\n",
    "            correct += (predicted == labels).sum().item()\n",
    "            # 更新进度条描述\n",
    "            progress_bar.set_postfix(loss=loss.item())\n",
    "\n",
    "    accuracy = 100 * correct / total\n",
    "    avg_loss = running_loss / len(dataloader)\n",
    "    return avg_loss, accuracy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "59d90b49-560f-40b2-a1cd-d2fe798c8910",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/5\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Training:   0%|                                                                               | 0/9373 [00:00<?, ?it/s]D:\\develop\\anaconda\\envs\\pytorch\\lib\\site-packages\\torch\\nn\\functional.py:5560: UserWarning: 1Torch was not compiled with flash attention. (Triggered internally at C:\\cb\\pytorch_1000000000000\\work\\aten\\src\\ATen\\native\\transformers\\cuda\\sdp_utils.cpp:555.)\n",
      "  attn_output = scaled_dot_product_attention(q, k, v, attn_mask, dropout_p, is_causal)\n",
      "Evaluating:   0%|                                                                             | 0/1042 [00:00<?, ?it/s]D:\\develop\\anaconda\\envs\\pytorch\\lib\\site-packages\\torch\\nn\\modules\\transformer.py:409: UserWarning: The PyTorch API of nested tensors is in prototype stage and will change in the near future. (Triggered internally at C:\\cb\\pytorch_1000000000000\\work\\aten\\src\\ATen\\NestedTensorImpl.cpp:180.)\n",
      "  output = torch._nested_tensor_from_mask(output, src_key_padding_mask.logical_not(), mask_check=False)\n",
      "                                                                                                                       \r"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 1, Train_acc:77.0%, Train_loss:0.519, Test_acc:80.8%，Test_loss:0.439\n",
      "Epoch 2/5\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                                                       \r"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 2, Train_acc:81.0%, Train_loss:0.435, Test_acc:81.2%，Test_loss:0.439\n",
      "Epoch 3/5\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                                                       \r"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 3, Train_acc:81.6%, Train_loss:0.423, Test_acc:81.4%，Test_loss:0.438\n",
      "Epoch 4/5\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                                                       \r"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 4, Train_acc:82.1%, Train_loss:0.413, Test_acc:81.2%，Test_loss:0.439\n",
      "Epoch 5/5\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "                                                                                                                       "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 5, Train_acc:82.4%, Train_loss:0.406, Test_acc:81.5%，Test_loss:0.440\n",
      "训练完成!\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\r"
     ]
    }
   ],
   "source": [
    "# 开始训练\n",
    "\n",
    "num_epochs = 5\n",
    "\n",
    "train_loss = []\n",
    "train_acc  = []\n",
    "test_loss  = []\n",
    "test_acc   = []\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    print(f\"Epoch {epoch+1}/{num_epochs}\")\n",
    "    \n",
    "    epoch_train_loss, epoch_train_acc = train(train_loader, model, loss_fn, optimizer)\n",
    "\n",
    "    # 在测试集上评估\n",
    "    epoch_test_loss, epoch_test_acc = evaluate(test_loader, model, loss_fn)\n",
    "\n",
    "    train_acc.append(epoch_train_acc)\n",
    "    train_loss.append(epoch_train_loss)\n",
    "    test_acc.append(epoch_test_acc)\n",
    "    test_loss.append(epoch_test_loss)\n",
    "    # 打印训练和测试结果\n",
    "    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%，Test_loss:{:.3f}')\n",
    "    print(template.format(epoch+1, epoch_train_acc, epoch_train_loss, epoch_test_acc, epoch_test_loss))\n",
    "\n",
    "print(\"训练完成!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcb37d44-4e75-4a91-9041-2ffe8f030b7e",
   "metadata": {},
   "source": [
    "## 结果可视化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "21e1b89f-7b7f-4903-bddb-500fe3f39514",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA9EAAAEnCAYAAACuZMBeAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAACV9UlEQVR4nOzdd3hT5dvA8W+a7pW2dEMXs9CCbGQJyN6gooIyBETABfxUQPZGlKG+gouhgooCIgIiWxBQ9oYyS0tpKXTT3eS8f6QN3bRQSMf9ua5cTZ4855z7pGlz7jxLpSiKghBCCCGEEEIIIR7IxNgBCCGEEEIIIYQQZYUk0UIIIYQQQgghRBFJEi2EEEIIIYQQQhSRJNFCCCGEEEIIIUQRSRIthBBCCCGEEEIUkSTRQgghhBBCCCFEEUkSLYQQQgghhBBCFJEk0UIIIYQQQgghRBFJEi2EEEIIIYQQQhSRJNHlgEqlKtJt7969j3Sc6dOno1KpHmrbvXv3lkgMpd2QIUPw9fUt8Pk7d+5gbm7Oyy+/XGCd+Ph4rK2t6dWrV5GPu2rVKlQqFcHBwUWOJTuVSsX06dOLfLwst27dYvr06Zw8eTLPc4/yfikp6enpuLu7o1KpWLdunVFjEUKI0kSuHUoPuXa4z5jXDr6+vvTo0cMoxxZlj6mxAxCP7tChQzkez5o1iz179rB79+4c5XXq1Hmk4wwfPpwuXbo81LYNGzbk0KFDjxxDWefi4kKvXr3YuHEjMTExODo65qnz888/k5yczLBhwx7pWFOmTOHdd999pH08yK1bt5gxYwa+vr7Ur18/x3OP8n4pKZs3b+b27dsALF++nBdeeMGo8QghRGkh1w5lh1w7CFH6SBJdDjz99NM5Hru4uGBiYpKnPLekpCSsra2LfJwqVapQpUqVh4rR3t7+gfFUFMOGDWP9+vWsWbOGt956K8/zK1aswM3Nje7duz/ScapVq/ZI2z+qR3m/lJTly5djbm5OmzZt2L59Ozdv3jR6TPnRarVkZGRgYWFh7FCEEBWEXDuULXLtIETpIt25K4i2bdsSGBjIvn37aNGiBdbW1gwdOhSAtWvX0qlTJzw8PLCysqJ27dpMmDCBxMTEHPvIr4tNVteXbdu20bBhQ6ysrPD392fFihU56uXXJWvIkCHY2tpy5coVunXrhq2tLV5eXvzvf/8jNTU1x/Y3b97khRdewM7ODgcHB1555RWOHDmCSqVi1apVhZ77nTt3GD16NHXq1MHW1hZXV1eeffZZ9u/fn6NecHAwKpWKTz75hEWLFuHn54etrS3Nmzfn33//zbPfVatWUatWLSwsLKhduzbff/99oXFk6dy5M1WqVGHlypV5nrtw4QL//fcfgwYNwtTUlB07dtC7d2+qVKmCpaUl1atX54033uDu3bsPPE5+XbLi4+N5/fXXqVSpEra2tnTp0oVLly7l2fbKlSu89tpr1KhRA2traypXrkzPnj05c+aMoc7evXtp0qQJAK+99pqh619W16783i86nY4FCxbg7++PhYUFrq6uDBo0iJs3b+aol/V+PXLkCK1bt8ba2pqqVasyf/58dDrdA88d9N90b9u2jZ49e/L++++j0+kKfK/8+OOPNG/eHFtbW2xtbalfvz7Lly/PUWfbtm20b98ejUaDtbU1tWvXZt68eTlibtu2bZ595/49ZL3PFixYwOzZs/Hz88PCwoI9e/aQkpLC//73P+rXr49Go8HJyYnmzZvz+++/59mvTqfj888/p379+lhZWeHg4MDTTz/Npk2bAP0Fl5OTE0lJSXm2ffbZZwkICCjCqyiEqMjk2kGuHaBiXTs8SEpKChMnTsTPzw9zc3MqV67Mm2++SWxsbI56u3fvpm3btlSqVAkrKyu8vb15/vnnc3wmL1u2jKeeegpbW1vs7Ozw9/fnww8/LJE4xeMnSXQFEh4ezquvvsqAAQPYunUro0ePBuDy5ct069aN5cuXs23bNsaMGcMvv/xCz549i7TfU6dO8b///Y+xY8fy+++/U69ePYYNG8a+ffseuG16ejq9evWiffv2/P777wwdOpTFixfz0UcfGeokJibSrl079uzZw0cffcQvv/yCm5sbL730UpHii46OBmDatGls2bKFlStXUrVqVdq2bZvvOKsvvviCHTt2sGTJEtasWUNiYiLdunUjLi7OUGfVqlW89tpr1K5dm/Xr1zN58mRmzZqVpxtcfkxMTBgyZAjHjx/n1KlTOZ7L+nDMuki5evUqzZs3Z9myZWzfvp2pU6fy33//0apVK9LT04t0/lkURaFPnz788MMP/O9//+O3337j6aefpmvXrnnq3rp1i0qVKjF//ny2bdvGF198gampKc2aNSMoKAjQd7PLinfy5MkcOnSIQ4cOMXz48AJj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vnw577//PiEhITRo0MCwGoSvr69h+5CQEGrUqEGrVq1QqVSGOT8eJ/lUEUKIUk6rU4iITyEkKilHsqz/mczde4WvmaxSgZudZd6xyZkJs4utBSYygZcoR95qV4NNp24RGp3Mp7su82G32sYOSQghii338oFarZb58+ezdu1awsLCSE1NJTU1FRsbm0L3U69ePcP9rG7jkZGRRd4ma4nDyMhIvL29CQoKMiTlWZo2bcru3buLdF65Xb16lfT0dFq2bGkoMzMzo2nTply4cAGAUaNG8fzzz3P8+HE6depEnz59aNGiBQBDhgyhY8eO1KpViy5dutCjRw86der0ULEUlSTRQghhZIqiEJecbpjdOvsM16HRSYTFJhdpzWTvSvcn7fJyvD82WdZMFhWNlbmamb0CeW3VEZb/c52+DSpT20MmhBOiorAyU3N+ZmejHbuk5E6OFy5cyOLFi1myZAl169bFxsaGMWPGkJaWVuh+ck9IplKp0OkKnxg0+zZZ3dOzb5O7y/qjzFWdtW1++8wq69q1Kzdu3GDLli3s3LmT9u3b8+abb/LJJ5/QsGFDrl+/zp9//snOnTt58cUX6dChA+vWrXvomB6kWEl0RkYG06dPZ82aNURERODh4cGQIUOYPHkyJiYmpKenM3nyZLZu3cq1a9fQaDR06NCB+fPn4+np+bjOQQghSr2UdC1hsck5Ju3KviRUwgPWTDZTq6jskHPSruxjk2XNZCFyaufvSpcAd7adi2DyxrP8+kZz6XEhRAWhUqnKzOoPxbF//3569+7Nq6++CuiT2suXL1O79pPtbVOrVi0OHz7MwIEDDWVHjx596P1Vr14dc3Nz/vnnHwYMGADoZyM/evQoY8aMMdRzcXFhyJAhDBkyhNatW/P+++/zySefAGBvb89LL73ESy+9xAsvvECXLl2Ijo7GycnpoeMqTLHeXR999BFffvkl3333HQEBARw9epTXXnsNjUbDu+++S1JSEsePH2fKlCk89dRTxMTEMGbMGHr16vVIL6wQQpQVqRlaLoYncDosjrM347h+N5GQ6CRuJzx4zWQXO4scY5O9si0H5W5vKWsmC1FM03rVYf/lOxy7EcMvR0N5uam3sUMSQoiHVr16ddavX8/BgwdxdHRk0aJFREREPPEk+u233+b111+ncePGtGjRgrVr13L69GmqVq36wG2zL+OV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",
      "text/plain": [
       "<Figure size 1200x300 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "epochs_range = range(num_epochs)\n",
    "\n",
    "plt.figure(figsize=(12, 3))\n",
    "plt.subplot(1, 2, 1)\n",
    "\n",
    "plt.plot(epochs_range, train_acc, label='Training Accuracy')\n",
    "plt.plot(epochs_range, test_acc, label='Test Accuracy')\n",
    "plt.legend(loc='lower right')\n",
    "plt.title('Training and Validation Accuracy')\n",
    "\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.plot(epochs_range, train_loss, label='Training Loss')\n",
    "plt.plot(epochs_range, test_loss, label='Test Loss')\n",
    "plt.legend(loc='upper right')\n",
    "plt.title('Training and Validation Loss')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c30a8730-16cc-4cca-b5b7-d74bb3ed1948",
   "metadata": {},
   "source": [
    "## 保存模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "3e6208f1-878a-4365-9791-792833fcd9dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 指定保存路径\n",
    "save_dir = '../models/3_Transformer_Sentiment_Classification'\n",
    "\n",
    "# 确保目录存在，如果不存在则创建\n",
    "import os\n",
    "if not os.path.exists(save_dir):\n",
    "    os.makedirs(save_dir)\n",
    "\n",
    "# 保存模型\n",
    "torch.save(model.state_dict(), os.path.join(save_dir, 'model_weights.pth'))\n",
    "\n",
    "# # 加载模型参数\n",
    "# model.load(torch.load(os.path.join(save_dir, 'model_weights.pth')))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
